
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include "shapefile.h"

#ifndef MAX
#	define MIN(a, b)	((a < b) ? a : b)
#	define MAX(a, b)	((a > b) ? a : b)
#endif

static void swap_word (int length, void *word)
{
	int i;
	unsigned char tmp;
	
	for (i = 0; i < length / 2; i++) {
		tmp = ((unsigned char *) word)[i];
		((unsigned char *) word)[i] = ((unsigned char *) word)[length - i - 1];
		((unsigned char *) word)[length - i - 1] = tmp;
	}
}

void geopoint_shapefile_init (geopoint_shapefile_t *sf)
{
	sf->shp_fp = NULL;
	sf->shx_fp = NULL;
}

int geopoint_shapefile_open (geopoint_shapefile_t *sf, const char *filename)
{
	char *full_filename;
	unsigned char *buf;
	int i, big_endian;
	double value;
	
	/* establish the byte order on this machine
	 */
	i = 1;
	if (*((unsigned char *) &i) == 1) {
		big_endian = 1;
	} else {
		big_endian = 0;
	}
	
	/* allocate some memory for the full filename
	 */
	full_filename = (char *) malloc (strlen(filename) + 5);
	
	/* open the .shp file
	 */
	sprintf (full_filename, "%s.shp", filename);
	sf->shp_fp = fopen (full_filename, "rb");
	if (sf->shp_fp == NULL) {
		return -1;
	}
	
	/* open the .shx file
	 */
	sprintf (full_filename, "%s.shx", filename);
	sf->shx_fp = fopen (full_filename, "rb");
	if (sf->shx_fp == NULL) {
		geopoint_shapefile_close (sf);
		return -1;
	}
	
	free (full_filename);
	
	/* read the file size from the .shp file
	 */
	buf = (unsigned char *) malloc (100);
	fread (buf, 100, 1, sf->shp_fp);
	sf->filesize = (buf[24] * 256 * 256 * 256
		+ buf[25] * 256 * 256
		+ buf[26] * 256
		+ buf[27]) * 2;
	
	/* read .shx file header info
	 */
	fread (buf, 100, 1, sf->shx_fp);
	if (buf[0] != 0 || buf[1] != 0 || buf[2] != 0x27
		|| (buf[3] != 0x0a && buf[3] != 0x0d)) {
			geopoint_shapefile_close (sf);
			free (buf);
			return -1;
	}
	
	sf->num_records = buf[27]
		+ buf[26] * 256
		+ buf[25] * 256 * 256
		+ buf[24] * 256 * 256 * 256;
	sf->num_records = (sf->num_records * 2 - 100) / 8;
	
	sf->shape_type = buf[32];
	
	if (sf->num_records < 0 || sf->num_records > 256000000) {
		geopoint_shapefile_close (sf);
		free (buf);
		return -1;
	}
	
	/* read the bounds
	 */
	if (big_endian) swap_word (8, buf + 36);
	memcpy (&value, buf + 36, 8);
	sf->min_bounds[0] = value;
	
	if (big_endian) swap_word (8, buf + 44);
	memcpy (&value, buf + 44, 8);
	sf->min_bounds[1] = value;
	
	if (big_endian) swap_word (8, buf + 52);
	memcpy (&value, buf + 52, 8);
	sf->max_bounds[0] = value;
	
	if (big_endian) swap_word (8, buf + 60);
	memcpy (&value, buf + 60, 8);
	sf->max_bounds[1] = value;
	
	if (big_endian) swap_word (8, buf + 68);
	memcpy (&value, buf + 68, 8);
	sf->min_bounds[2] = value;
	
	if (big_endian) swap_word (8, buf + 76);
	memcpy (&value, buf + 76, 8);
	sf->max_bounds[2] = value;
	
	if (big_endian) swap_word (8, buf + 84);
	memcpy (&value, buf + 84, 8);
	sf->min_bounds[3] = value;
	
	if (big_endian) swap_word (8, buf + 92);
	memcpy (&value, buf + 92, 8);
	sf->max_bounds[3] = value;
	
	free (buf);
	
	/* read the .shx file to get the offsets to each record in the
	 * .shp file.
	 */
	sf->num_max_records = sf->num_records;
	
	sf->pan_rec_offset =
		(int *) malloc (sizeof(int) * MAX(1, sf->num_max_records));
	sf->pan_rec_size =
		(int *) malloc (sizeof(int) * MAX(1, sf->num_max_records));
	
	buf = (unsigned char *) malloc (8 * MAX(1, sf->num_records));
	fread (buf, 8, sf->num_records, sf->shx_fp);
	
	for (i = 0; i < sf->num_records; i++) {
		int offset, length;
		
		memcpy (&offset, buf + i * 8, 4);
		if (!big_endian) {
			swap_word (4, &offset);
		}
		
		memcpy (&length, buf + i * 8 + 4, 4);
		if (!big_endian) {
			swap_word (4, &length);
		}
		
		sf->pan_rec_offset[i] = offset * 2;
		sf->pan_rec_size[i] = length * 2;
	}
	
	free (buf);
	
	printf ("%s, size=%d, num_records=%d, shape_type=%d\n",
		filename, sf->filesize, sf->num_records, sf->shape_type);
	
	return 0;
}

void geopoint_shapefile_close (geopoint_shapefile_t *sf)
{
	free (sf->pan_rec_offset);
	free (sf->pan_rec_size);
	
	fclose (sf->shp_fp);
	fclose (sf->shx_fp);
}
